/* 
 *  Copyright 2012 CodeMagi, Inc.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.codemagi.servlets.newsletter;

import com.codemagi.login.AccessException;
import com.codemagi.login.LoginException;
import com.codemagi.login.model.User;
import com.codemagi.servlets.AppException;
import com.codemagi.servlets.SecureException;
import com.codemagi.servlets.ServletUtils;
import com.codemagi.servlets.newsletter.model.*;
import com.codemagi.util.*;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.log4j.*;
import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.OQLQuery;
import org.exolab.castor.jdo.PersistenceException;
import org.exolab.castor.jdo.QueryResults;

/**                                                                                                        
 * Class extending the <code>HttpServlet</code> to implement                                                            
 * the main controller for newsletter subscription and sending operations.   
 * <p>                                                                                                              
 * Please see the related documentation for more detailed                                                      
 * information on process flow and functionality.                                                                
 *                                                                                                                   
 * @version 1.0                                                                                   
 * @author Jim Manico for CodeMagi, Inc.                                                                                  
 */
public class NewsletterController extends com.codemagi.servlets.BaseServlet {

    //set to true to view debug output                                                                              
    Logger log = Logger.getLogger(this.getClass());

    //VIEWS specific to this application
    protected static final String VIEW_MAIN              = "/form_signup.jsp";
    protected static final String VIEW_REMOVE            = "/remove.jsp";
    protected static final String VIEW_FORM_SIGNUP       = "/form_signup.jsp";
    protected static final String VIEW_FORM_NEWSLETTER   = "/admin/newsletter.jsp";

    //MESSAGES specific to this application
    protected static final String MSG_SIGNUP_SUCCESS     = "Signup form submitted successfully.";
    protected static final String MSG_REMOVE_SUCCESS     = " has been unsubscribed.";
    protected static final String MSG_EMAIL_FAILED       = "The attempt to signup has failed. Your request has not been submitted.";
    protected static final String MSG_REMOVE_ERROR       = "Error trying to unsubscribe. The URL you entered has expired.";
    protected static final String MSG_SIGNUP_DUPLICATE   = "This email address has already been subscribed to this topic.";
    protected static final String MSG_NEWSLETTER_SUCCESS = " newsletters were submitted successfully.";
    protected static final String ACCESS_ERROR           = "You do not have permission to access this resource";

    private static final String NEWSLETTER_GROUP         = "NEWSLETTER_ADMIN";
    
    private static final Class SIGNUP_CLASS              = com.codemagi.servlets.newsletter.model.Signup.class;
    private static final Class GROUP_CLASS               = com.codemagi.login.model.Group.class;

    private static String pAdminEmail                    = null;
    private static String mailhost                       = "localhost";
    private static String physicalAddress                = "";

    /** SQL query to select all newsletter topics */
    public static final String NEWSLETTER_TOPICS =
        " SELECT topic_id, topic_name FROM newsletter_topic ORDER BY sort_order ASC ";

    /** SQL Query to select all newsletter topics and the number of subscribers for each */
    public static final String NEWSLETTER_TOPICS_ADMIN =
        " SELECT nt.topic_id, CONCAT(MAX(nt.topic_name), ' (', COUNT(s.email), ' Subscribers)') AS topic_name " +
        " FROM newsletter_topic nt LEFT JOIN signup s ON nt.topic_id = s.topic_id " +
        " GROUP BY nt.topic_id " +
        " ORDER BY MAX(nt.sort_order) ASC ";

    /**
     * Initializes the servlet when it is loaded by the
     * servlet engine.
     *
     * @param config the configuration as <code>ServletConfig</code>
     *
     * @throws ServletException if initialization fails.
     */
    public void init(ServletConfig config)
        throws ServletException {

        super.init(config);

	//administrative email that is the source of all newsletter blasts 
        pAdminEmail = config.getInitParameter("adminEmail");
        if (Utils.isEmpty(pAdminEmail)) throw new ServletException("adminEmail config parameter is required for the NewsletterController");

	//mail host for email: Check servlet config, then try app config
	String pMailhost = config.getInitParameter("mailhost");
        if (Utils.isEmpty(pMailhost)) {
	    pMailhost = application.getInitParameter("mailhost");
	}
	if (!Utils.isEmpty(pMailhost)) mailhost = pMailhost;

	//physical address
	physicalAddress = Utils.noNulls( config.getInitParameter("physicalAddress") );

	log.debug("CONFIG SETTINGS:   " + pAdminEmail + "|" + pMailhost + "|" + physicalAddress);
    }


    /**                                                                                                             
     * Handles the incoming requests.  
     *                                                                                    
     * @param req a reference to the actual <code>HttpServletRequest</code>                                                
     *        instance.                                                                                                
     * @param res a reference to the actual <code>HttpServletResponse</code>                                          
     *        instance.                                                                                               
     *                                                                                                                      
     * @throws ServletException if servlet related operations fail.                                      
     * @throws IOException if I/O operations fail.                                                                          
     */
    public void service(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

        HttpSession session = request.getSession(true);

        String action  = request.getParameter("acton");

        log.debug("ACTON:   " + action);
	ServletUtils.logRequestParameters(request);
	ServletUtils.logRequestAttributes(request);
	ServletUtils.logSessionAttributes(request);

        String nextPage = VIEW_MAIN;

        try {
	    forceSecure(request, response);

            //dispatch requests to the proper method                                                                     
	    if ("signup".equals(action)) {
		nextPage = dispatchSignup(request, response);
	    
	    } else if ("newsletter".equals(action)) {
		nextPage = dispatchNewsletter(request, response);
	    
	    } else {

		//if we have the get ID then this is a unsubscribe from newsletter request
		String  removeId = request.getParameter("id");
		if (removeId != null) {
		    nextPage = dispatchRemove(request, response, removeId);
		}
	    }

        } catch (AccessException ae) {
	    nextPage = handleAccessException(request, response, ae);

        } catch (LoginException le) {
	    nextPage = handleLoginException(request, response, le);

        } catch (SecureException se) {
            log.debug("Request forwarded to https");
            nextPage = VIEW_NONE;

        } catch (AppException ape) {
            log.debug("", ape);
            request.setAttribute("error_message", ape.getMessage());

        }

	//forward on
        log.debug("Forwarding to: " + nextPage);

	if (!VIEW_NONE.equals(nextPage)) {
	    RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(nextPage);
	    dispatcher.forward(request, response);
	}

        return;
    }


    //DISPATCHERS -----------------------------------------------------------------------------

    /**                                                                                                                  
     * Dispatches <code>newsletter</code> actions. Actually send the newsletters to people who have signed up. 
     */
    private String dispatchNewsletter(HttpServletRequest request, HttpServletResponse response)
        throws LoginException, AccessException {

	User user = (User)request.getSession().getAttribute("user");
	if (!user.isInGroup(NEWSLETTER_GROUP)) throw new AccessException(ACCESS_ERROR);

        log.debug("***** dispatchNewsletter *****");

        //next page if operation is successful                                          
        String nextPage = request.getParameter("returnPage");
        request.setAttribute("returnPage", nextPage);
        if (Utils.isEmpty(nextPage)) nextPage = VIEW_FORM_NEWSLETTER;

        Database db = null;

        try {

	    //get params from request
	    Integer topicId  = convertInteger( request.getParameter("topicId"), true );
	    String  message  = request.getParameter("message");
	    String  subject  = request.getParameter("subject");

	    //check required
	    String[] required = { subject, message };
	    checkRequired(required);
	    if (topicId < 1) {
		throw new AppException("topicId is required");
	    }

	    //get database instance
	    //NOTE: Don't use jdo.getDatabase() any more. Use getJDODatabase() instead. 
	    db = getJDODatabase();
	    db.begin();

	    //load dependent objects 
	    NewsletterTopic topic = (NewsletterTopic)db.load(NewsletterTopic.class, topicId);

	    //TODO save copy of newsletter for records
	    Newsletter newsletter = new Newsletter();
	    newsletter.setMessage(message);
	    newsletter.setSubject(subject);
	    newsletter.setTopic(topic);
	    db.create(newsletter);

	    //get all users for this topic
	    OQLQuery oql = db.getOQLQuery(" SELECT s FROM " + SIGNUP_CLASS.getName() + " s " +
					  " WHERE s.topic.id = $1" );
	    oql.bind(topicId);
	    QueryResults results = oql.execute();

	    // create collection here
	    Vector<Signup> signups = new Vector<Signup>(100);
	    // loop through oql results and add to collection
	    while (results.hasMore()) {
		Signup signup = (Signup)results.next();
		signups.add(signup);
	    }
	    
	    //commit newsletter and close db
            db.commit();
            closeJDODatabase(db);

	    //loop through all emails and do the send
	    int good = 0;
	    int bad = 0;

            for (Signup signup : signups) {
		boolean goodSend = sendNewsletter(subject, message, signup.getEmail(), signup.getRemoveHash(), getServerName(request));

		if (goodSend) {
		    good++;
		} else {
		    bad++;
		}
	    }

	    request.setAttribute("error_message", good + MSG_NEWSLETTER_SUCCESS);

        } catch (AppException ae) {
            log.debug("", ae);
            request.setAttribute("error_message", ae.getMessage());
	    nextPage = VIEW_FORM_NEWSLETTER;

        } catch (PersistenceException e) {
	    handlePersistenceException(request, db, e);
	    nextPage = VIEW_FORM_NEWSLETTER;

        } finally {
            closeJDODatabase(db);
        }

	log.debug("ERROR: " + request.getAttribute("error_message"));
        return nextPage;
    }

    /**                                                                                                                  
     * Dispatches <code>signup</code> actions.
     */
    private String dispatchSignup(HttpServletRequest request, HttpServletResponse response)
        throws LoginException, AccessException {

        log.debug("***** dispatchSignup *****");

        //next page if operation is successful                                          
        String nextPage = request.getParameter("returnPage");
        request.setAttribute("returnPage", nextPage);
        if (Utils.isEmpty(nextPage)) nextPage = VIEW_FORM_SIGNUP;

        Database db = null;

        try {

	    //get params from request
	    Integer topicId  = convertInteger( request.getParameter("topicId"), true );
	    String  email    = request.getParameter("email");

	    //check required
	    String[] required = { email };
	    checkRequired(required);

	    //force to lowercase
	    email = email.toLowerCase();

	    //check addl
	    checkValidEmail(email);

	    //get database instance
	    db = jdo.getDatabase();
	    db.begin();


	    //make sure this user is not already signed up for this topic
	    log.debug("About to check for duplicate subscription for email addy " + email);
	    boolean gotDuplicate = false;
	    OQLQuery oql = db.getOQLQuery(" SELECT s FROM " + SIGNUP_CLASS.getName() + " s " +
					  " WHERE s.topic.id = $1 and s.email = $2 " );


	    log.debug("OQL query executed checking for duplicate subscription");
	    oql.bind(topicId);
	    oql.bind(email);
	    
	    log.debug("OQL query vars set, about to execute with parameters = " + topicId + "|" + email);

	    QueryResults results = oql.execute();

	    log.debug("OQL query vars set execute complete");

	    while (results.hasMore()) {
		results.next();
		log.debug("inside OQL loop, duplicate found");

		gotDuplicate = true;
		request.setAttribute("error_message", MSG_SIGNUP_DUPLICATE);
	    }

	    log.debug("gotDuplicate: " + gotDuplicate);
	    
	    //if this is not a dupe, sign the user up
	    if (gotDuplicate == false) {
		
		//create the signup Object
		Signup signup = new Signup();
		
		//load dependent objects 
		log.debug("About to load NewsletterTopic for topicId " + topicId);
		NewsletterTopic topic = (NewsletterTopic)db.load(NewsletterTopic.class, topicId);
		signup.setTopic(topic);
		
		//set data for Signup object
		signup.setEmail(email);
		signup.setRemoveHash(UIDGenerator.getUID());
		
		//create the new object in the DB if needed
		db.create(signup);
		db.commit();
		
		request.setAttribute("error_message", MSG_SIGNUP_SUCCESS);
	    }
	 	    
        } catch (AppException ae) {
            log.debug("", ae);
            request.setAttribute("error_message", ae.getMessage());
	    nextPage = VIEW_FORM_SIGNUP;

        } catch (PersistenceException e) {
	    handlePersistenceException(request, db, e);
	    nextPage = VIEW_FORM_SIGNUP;

        } finally {
            closeJDODatabase(db);
        }

	log.debug("ERROR: " + request.getAttribute("error_message"));
        return nextPage;
    }



    private String dispatchRemove(HttpServletRequest request, HttpServletResponse response, String removeHash)
        throws LoginException, AccessException {

        log.debug("***** dispatchRemove *****");

        //Next page if operation is successful                                          
        String nextPage = VIEW_REMOVE;

        Database db = null;

        try {

	    //get database instance
	    db = jdo.getDatabase();
	    db.begin();
	    
	    String email = null;

	    //delete all notes
	    OQLQuery oql = db.getOQLQuery(" SELECT s FROM " + SIGNUP_CLASS.getName() + " s " +
					  " WHERE s.removeHash = $1 " );
	    oql.bind(removeHash);
	    QueryResults results = oql.execute();
	    while (results.hasMore()) {
		Signup s = (Signup)results.next();
		email = s.getEmail();
		db.remove( s );
	    }
	    
	    //if no email was set, no subscript exists for that hash
	    if (email == null) {
		throw new AppException(MSG_REMOVE_ERROR);
	    }

	    //commit
	    db.commit();
		    
	    request.setAttribute("error_message", email + MSG_REMOVE_SUCCESS);

        } catch (AppException ae) {
            log.debug("", ae);
            request.setAttribute("error_message", ae.getMessage());
	    nextPage = VIEW_REMOVE;

        } catch (PersistenceException e) {
	    handlePersistenceException(request, db, e);
	    nextPage = VIEW_REMOVE;

        } finally {
            closeJDODatabase(db);
        }

	log.debug("ERROR: " + request.getAttribute("error_message"));

        return nextPage;

    }

    private boolean sendNewsletter(String subject, String message, String email, String removeHash, String serverName) {
	log.debug("try sending newsletter to " + email);

	//send an email to the alias
	String from = pAdminEmail + " <" + pAdminEmail + ">";
	String to   = email;

	//create buffers for the email body in both plaintext and HTML
	StringBuffer textBody = new StringBuffer(1024);
	StringBuffer htmlBody = new StringBuffer(1024);

	//add the submitted message to the body
	textBody.append( StringUtils.stripHtmlRegex(message) ).append("\r\r");
	htmlBody.append( message ).append("<br><br>");

	//add the remove URL
	String urlBase = "http://" + serverName + "/signup?id=" + removeHash;

	textBody
	    .append("If you would like to unsubscribe from this newsletter, please go to ")
	    .append( urlBase );
	htmlBody
	    .append("If you would like to unsubscribe from this newsletter, please go to <a href=\"")
	    .append( urlBase )
	    .append("\">")
	    .append( urlBase )
	    .append("</a>");

	//add the physical address if specified
	if (!Utils.isEmpty(physicalAddress) ) {
	    textBody.append("\r\r")
		.append( StringUtils.stripHtmlRegex(physicalAddress) );
	    htmlBody.append("<br><br>")
		.append( physicalAddress );
	}

	boolean emailOkay = EmailUtils.sendEmail(from, to, subject, textBody, htmlBody, mailhost, "");
	log.debug("done sending newsletter to " + email + "(" + emailOkay  + "}");

	return emailOkay;
    }


    //UTILITY METHODS -------------------------------------------------------

    /**                                                                                                                
     * Returns the server name portion of the request URL.                                                                
     * For example, if the request URL is:                                                                                 
     * <BR><BR>                                                                                                        
     * http://www.codemagi.com/servlet?param1=value1                                                                   
     * <BR><BR>                                                                                                       
     * Then this method will return:                                                                                     
     * <BR><BR>                                                                                                            
     * www.codemagi.com                                                                                                
     */

    public static String getServerName(HttpServletRequest request) {
	return request.getServerName();
    }


}
